/*
 * CubePuzzle - A 3D puzzle game
 * Copyright (C) 2010  V.Vamsi Krishna
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "puzzle.h"

//initializing global variable
bool g_EnableGrid = false;
double modelview[16], projection[16];
int viewport[4];
TTFFont *g_HudFont = NULL;
TTFFont *g_MenuFont= NULL;

/* Puzzle constructor 
	1) Allocate memory for grid
	2) Initialize the array (sequential numbers)
	3) Scatter them in the array.
	4) Create layers randomly i.e, diff sizes
	5) pick layers randomly and rotate by 90
*/
Puzzle::Puzzle(int size, char nlayers)
:size(size)
{
	grid = new unsigned int [size * size];
	if(grid == NULL)
	{
		std::cout << "Could not allocate memory" << std::endl;
		exit(1);
	}

	//sequential initialization
	for(int i = 0; i < size * size; i++)
	{
		grid[i] = i;
	}

	randomizeGrid( nlayers );
	//Create the layers.
	//TODO: create layers with diff lengths
	int lsize = size * size / nlayers;
	std::cout << lsize << std::endl;
	for(int i = 0; i < nlayers; i++)
	{
		layers.push_back((i + 1) * lsize);
	}
    layers[nlayers-1] = size * size;

RANDOM_ROTATION:
	//pick layer randomly and rotate by 90
	for(int i = 0; i < nlayers * size; i++)
	{
		int layer_index = (int)((1.0 * nlayers) * (rand() / (RAND_MAX + 1.0)));
		rotLayer(layer_index, layer_index&1);
	}
    if(isSolved())
        goto RANDOM_ROTATION;
}

void Puzzle::randomizeGrid( char nlayers )
{
    srand((int)time(0));
	//Scatter the values in the array (loop is just a hueristic can be any number of times)
	for(int i = 0; i < size * nlayers; i++)
	{
		int index = (int) ((1.0 * size * size) * (rand() / (RAND_MAX + 1.0)));
		unsigned int tmp = grid[index];
		grid[index] = grid[0];
		grid[0] = tmp;
	}
}

void Puzzle::render(float *angles)
{
	GLRenderer::renderGrid(angles, grid, size, layers);
}

/*check if there are duplicates in the values created for each layer.
  If there are duplicates it means the puzzle isn't solved. This is true
  because we create the puzzle in such a way that there is only one solution.
 */
bool Puzzle::isSolved()
{
	int ss = size * size;
	bool *buff = new bool[ss];
	memset(buff, false, ss * sizeof(bool));

	for(int i = 0; i < ss; i++)
	{
		if( !buff[grid[i]] )
		{
			buff[grid[i]] = true;
		}
		else
		{
			delete [] buff;
			return false;
		}
	}
	delete [] buff;
	return true;
}

/* rotate a particular layer, clock or anti-clock as seen from top view*/
void Puzzle::rotLayer(int layer_index, bool clockwise)
{
	int strt_indx = 0;
	int end_indx = layers[layer_index];

	if( layer_index > 0)
	{
		strt_indx = layers[layer_index-1];
	}

	for(int i = strt_indx; i < end_indx; i++)
	{
		int x, y, x1, y1;
		x = grid[i]%size;
		y = grid[i]/size;

		if(clockwise) //pre caliculate x1 = h - y  and y1 = x
		{
			x1 = (size-1) - y;
			y1 = x;
		}
		else //pre caliculated x1 = y and y1 = h - x
		{
			x1 = y;
			y1 = (size-1) - x;
		}
		grid[i] = y1 * size + x1;
	}
}

/*printing the puzzle data created from the initialization*/
void Puzzle::dumpData()
{
	for(int i = 0; i < size * size; i++)
	{
		std::cout << grid[i] << " ";
	}
	std::cout << "\n";
	for(unsigned int i = 0, index = 0; i < layers.size(); i++)
	{
		char **buff = new char* [size];
		for(int j = 0; j < size; j++)
		{
			buff[j] = new char [size+1];
			memset(buff[j], '#', size * sizeof(char));
			buff[j][size] = '\0';
		}
		for(int j = index; j < layers[i]; j++)
		{
			std::cout << grid[j] << " ";
			int x = grid[j] % size;
			int y = grid[j] / size;
			buff[y][x] = 'x';
		}
		std::cout << "\n";
		for(int j = 0; j < size; j++)
		{
			std::cout << buff[j] << "\n";
			delete [] buff[j];
		}
		delete [] buff;
		index = layers[i];
	}
}

int Puzzle::getSize()
{
	return size;
}

unsigned int Puzzle::getNumLayers()
{
	return layers.size();
}

Puzzle::~Puzzle()
{
	delete [] grid;
}

/* Abstracting the rendering from the puzzle class*/
#include <GL/gl.h>
#include <GL/glu.h>

void GLRenderer::init(int w, int h)
{
	glClearColor(0, 0, 0, 0);
    glEnable(GL_DEPTH_TEST);
	glViewport(0, 0, w, h);
	GLRenderer::width = w;
	GLRenderer::height = h;

    //animation data rotating a layer[indx]
    indx = -1;
    langle = 0;

    //texturing details
    glEnable(GL_TEXTURE_2D);
    pTexObj = new Texture("images/cubetex.tga");
    pTexObj->Load();

    //load the fonts required for rendering text
//    g_HudFont = new TTFFont("fonts/01\ Digit.ttf", 15, 0);
    g_HudFont = new TTFFont("fonts//larabieb.ttf", 20, 0);
}

void GLRenderer::set2DProjection(int w, int h)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, WIDTH, 0, HEIGHT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void GLRenderer::set3DProjection(int w, int h)
{
    resize(w, h);
}
void GLRenderer::resize(int w, int h)
{
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	//gluPerspective(60.0, w/(double)h, 0.1f, 600.0f);
	double top = tan(60.0 *3.14159/360.0) * 0.1f;
    double bottom = -top;
    double left = bottom; //w/(double)h * bottom;
    double right = top; //w/(double)h * top;
	glFrustum(left, right, bottom, top, 0.1f, 600.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

    //get the projection matrix		
    glGetDoublev( GL_PROJECTION_MATRIX, projection );
    //get the viewport		
    glGetIntegerv( GL_VIEWPORT, viewport );
}

void GLRenderer::setEyeAndCenter(int size, int layers)
{
	cx = (size * block_size) / 2.0f;
	cy = (layers * layer_gap) / 2.0f;
	cz = size * block_size * 3.0f;

    ex = cx;
    ey = cy/2.0f;
    ez = cz;
    
    cy /= 2.0f;
    cz = size * block_size / 2.0f;
}
void GLRenderer::setCamera()
{
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//gluLookAt(cx/2, cy, cz, cx/2, cy/2, 0, 0, 1, 0);
	//gluLookAt(ex, ey, ez, cx, cy, cz, 0, 1, 0);
	glTranslatef(-ex, -ey, -ez);
} 

void GLRenderer::drawCube(float x, float y, float z, float w, float h)
{

    static GLfloat vertices[] = {1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
                             1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0,
                             1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
                             -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0,
                             -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0,
                             -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0};

    static GLfloat texels[] = { 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
                                1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 };

   glEnableClientState(GL_VERTEX_ARRAY);
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);

   glVertexPointer(3, GL_FLOAT, 0, vertices);
   glTexCoordPointer(2, GL_FLOAT, 0, texels);
   glPushMatrix();
   glTranslatef(x+h/2, y+h/2, z+h/2);
   //glColor4f(0.2f, 0.5f, 1.0f, 1.0f);
   glDrawArrays(GL_QUADS, 0, 24);
   glPopMatrix();
   glDisableClientState(GL_VERTEX_ARRAY);
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}

 
#include <math.h>	
void GLRenderer::renderGrid(float *angles, unsigned int *grid, int size, std::vector<int> &layers)
{
    glPushMatrix();
    //glLoadIdentity();
    glTranslatef(cx, cy/2, cz);
    glRotatef(angles[0], 0, 1, 0);
    glRotatef(angles[1], cos(angles[0] * 3.14f/180), 0, sin(angles[0] * 3.14f/180));
    glTranslatef(-cx, -cy, -cz);

    //read info required for picking
    //get the modelview matrix		
    glGetDoublev( GL_MODELVIEW_MATRIX, modelview );

	for(unsigned int k = 0; k < layers.size(); k++)
	{
		int sindx = 0;
		int eindx = layers[k];
		int y = k * (int)layer_gap;
        //int cx = size * block_size / 2;
        //int cz = size * block_size / 2;

        if(k == GLRenderer::indx)
        {
            glPushMatrix();
            glTranslatef(cx, 0, cx);
            //glTranslatef(cx, 0, cz);
            glRotatef((float)GLRenderer::langle, 0, 1, 0);
            glTranslatef(-cx, 0, -cz);
        }

        if(g_EnableGrid)
        {
            //get rid of texturing before we draw the black lines
            glDisable(GL_TEXTURE_2D);
	        glColor4f(1, 1, 1, 1);
	        glBegin(GL_LINES);
	        for(int i = 0; i <= size; i++)
	        {			
		        glVertex3i(i * block_size, y, 0);
		        glVertex3i(i * block_size, y, size * block_size);

		        glVertex3i(0, y, i * block_size);
		        glVertex3i(size * block_size, y, i * block_size);			
	        }
	        glEnd();
            //enable it once we are done drawing black lines.
            glEnable(GL_TEXTURE_2D);
        }
 
		if(k > 0)
			sindx = layers[k-1];
       
        glBindTexture(GL_TEXTURE_2D, pTexObj->GetTexID());
		for(int j = sindx; j < eindx; j++)
		{
			int x = grid[j] % size;
			int z = grid[j] / size;

			x = x * block_size;
			z = z * block_size;
			GLRenderer::drawCube((float)x, (float)y, (float)z, (float)block_size, (float)block_size);
		}

       if(k == GLRenderer::indx)
        {
            glPopMatrix();
        }
	}
    glPopMatrix();
}

void GLRenderer::setIndex(int index)
{
    indx = index;
}
int GLRenderer::getIndex()
{
    return indx;
}

bool GLRenderer::updateAngle()
{
    langle += 10;
    if(langle >= 90)
    {
        //indx = -1;
        langle = 0;
        return false;
    }
    return true;
}
